package com.ccx.demo.business.common.entity;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import com.google.common.collect.Lists;
import com.querydsl.core.annotations.QueryEntity;
import com.querydsl.core.annotations.QueryTransient;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.BeanPath;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import com.querydsl.jpa.impl.JPAUpdateClause;
import com.support.mvc.encrypt.AesApiId;
import com.support.mvc.entity.ITable;
import com.support.mvc.entity.IWhere;
import com.support.mvc.entity.IWhere.QdslWhere;
import com.support.mvc.entity.base.OrderBy;
import com.utils.enums.Code;
import com.utils.util.Dates;
import com.utils.util.Then;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.ToString;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.domain.Sort;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import javax.validation.constraints.Positive;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static com.ccx.demo.business.common.entity.QTabErrorLog.tabErrorLog;
import static com.utils.util.Dates.Pattern.yyyyMMddHHmmss;
import static com.utils.util.Dates.Pattern.yyyyMMddHHmmssSSS;
import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY;

/**
 * 实体类：异常日志记录
 *
 * @author 谢长春 on 2022-02-16 V20220301
 */
@Table(name = "tab_error_log")
@Entity
@QueryEntity
@DynamicInsert
@DynamicUpdate
@Getter
@Setter
@ToString
@ApiModel(description = "异常日志记录")
@JSONType(orders = {"id", "traceId", "message", "createTime", "createUserId", "updateTime", "updateUserId", "deleted"})
public class TabErrorLog implements
        ITable, // 所有与数据库表 - 实体类映射的表都实现该接口；方便后续一键查看所有表的实体
        // JPAUpdateClause => 需要的动态更新字段；采用 方案2 时需要实现该接口
        // QdslWhere       => 动态查询条件
        IWhere<JPAUpdateClause, QdslWhere> {
    private static final long serialVersionUID = 1L;

    /**
     * 数据ID，主键自增
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    @Positive
    @ApiModelProperty(value = "数据ID", position = 1)
    private Long id;
    /**
     * 链路追踪id
     */
    @Column(name = "traceId")
    @Size(max = 64)
    @ApiModelProperty(value = "链路追踪id", position = 2)
    private String traceId;
    /**
     * 日志内容
     */
    @Column(name = "message")
    @Size(max = 262140)
    @ApiModelProperty(value = "日志内容", position = 3)
    private String message;
    /**
     * 错误发生时间，格式：yyyyMMddHHmmss
     */
    @Column(name = "createTime")
    @Size(min = 14, max = 14)
    @ApiModelProperty(value = "数据新增时间，格式：yyyyMMddHHmmss", example = "20200202020202", position = 4)
    private String createTime;
    /**
     * 创建用户ID
     */
    @Column(name = "createUserId")
    @PositiveOrZero
    @ApiModelProperty(value = "新增操作人id", position = 5)
    private Long createUserId;
    /**
     * 处理时间，yyyyMMddHHmmssSSS
     */
    @Column(name = "updateTime")
    @Size(min = 17, max = 17)
    @ApiModelProperty(value = "数据最后一次更新时间，格式：yyyyMMddHHmmssSSS", example = "20200202020202002", position = 6)
    private String updateTime;
    /**
     * 修改用户ID
     */
    @Column(name = "updateUserId")
    @PositiveOrZero
    @ApiModelProperty(value = "更新操作人id", position = 7)
    private Long updateUserId;
    /**
     * 是否逻辑删除。 [0:否,1:是]
     */
    @Column(name = "deleted")
    @ApiModelProperty(value = "是否逻辑删除。 [0:否,1:是]", position = 8)
    private Boolean deleted;

    /**
     * 扩展查询字段：按 id 批量查询，仅后端使用，对前端隐藏
     */
    @QueryTransient
    @Transient
    @ApiModelProperty(hidden = true)
    @JSONField(serialize = false, deserialize = false)
    private Set<Long> ids;
    /**
     * 排序字段
     */
    @QueryTransient
    @Transient
    @ApiModelProperty(value = "查询排序字段")
    private List<OrderBy> orderBy;

    /**
     * 防止踩坑。
     * 当 JPA 查询对象没有 clone 时，执行 setValue 操作时，会触发更新动作，这里拦截之后抛出异常
     */
    @PreUpdate
    public void onPreUpdate() {
        throw Code.A00003.toCodeException("禁止使用JPA默认的 update 方法。 如果业务逻辑确定需要使用 setValue 方式更新对象时，去掉这个异常");
    }

    /**
     * 加密id
     *
     * @return String
     */
    @Transient
    @QueryTransient
    @JSONField(deserialize = false) // 实体类只读方法一定要加禁止反序列化注解
    @ApiModelProperty(value = "加密id", position = 0, accessMode = READ_ONLY)
    public String getEncryptId() {
        return AesApiId.encrypt(id, getUpdateTime());
    }

    @SneakyThrows
    public TabErrorLog cloneObject() {
        return (TabErrorLog) super.clone();
    }

// Enum Start **********************************************************************************************************

    /**
     * 枚举：定义排序字段
     */
    public enum OrderByColumn {
        // 按 id 排序可替代按创建时间排序
        id(tabErrorLog.id),
//        traceId(tabErrorLog.traceId),
//        message(tabErrorLog.message),
//        createTime(tabErrorLog.createTime),
//        createUserId(tabErrorLog.createUserId),
//        updateTime(tabErrorLog.updateTime),
//        updateUserId(tabErrorLog.updateUserId),
//        deleted(tabErrorLog.deleted),
        ;
        private final ComparableExpressionBase<?> column;

        public OrderBy asc() {
            return new OrderBy().setName(this.name());
        }

        public OrderBy desc() {
            return new OrderBy().setName(this.name()).setDirection(Sort.Direction.DESC);
        }

        /**
         * 获取所有排序字段名
         *
         * @return {@link String[]}
         */
        public static String[] names() {
            return Stream.of(OrderByColumn.values()).map(Enum::name).toArray(String[]::new);
        }

        OrderByColumn(final ComparableExpressionBase<?> column) {
            this.column = column;
        }
    }

// Enum End : DB Start *************************************************************************************************

    /**
     * DAO层新增数据时，设置字段默认值
     *
     * @param now    当前时间
     * @param userId 当前操作用户id
     */
    public void insert(final Dates now, final Long userId) {
        this.id = null;
        this.deleted = Optional.ofNullable(this.deleted).orElse(false);
        this.createUserId = userId;
        this.createTime = now.format(yyyyMMddHHmmss);
        this.updateUserId = userId;
        this.updateTime = now.format(yyyyMMddHHmmssSSS);
        this.traceId = Optional.ofNullable(this.traceId).orElse(""); // 链路追踪id
        this.message = Optional.ofNullable(this.message).orElse(""); // 日志内容
    }

    @Override
    public Then<JPAUpdateClause> update(final JPAUpdateClause jpaUpdateClause) {
        final QTabErrorLog table = tabErrorLog;
        // 动态拼接 update 语句
        // 以下案例中 只有 name 属性 为 null 时才不会加入 update 语句；
        return Then.of(jpaUpdateClause)
//                .then(traceId, update -> update.set(table.traceId, traceId))
//                .then(message, update -> update.set(table.message, message))
//                .then(updateUserId, update -> update.set(table.updateUserId, updateUserId))
////                // 当 name != null 时更新 name 属性
////                .then(name, update -> update.set(table.name, name))
////                .then(update -> update.set(table.updateUserId, updateUserId))
////                // 假设数据库中 content is not null；可以在属性为null时替换为 ""
////                .then(update -> update.set(table.content, Optional.ofNullable(content).orElse("")))
////                // 数据库中 amount 可以为 null
////                .then(update -> update.set(table.amount, amount))
                ;
    }

    @Override
    public QdslWhere where() {
        final QTabErrorLog table = tabErrorLog;
        // 构建查询顺序规则请参考：com.support.mvc.entity.IWhere#where
        return QdslWhere.of()
                .andIfNonEmpty(ids, () -> table.id.in(ids))
                .and(id, () -> table.id.eq(id))
                .and(createUserId, () -> table.createUserId.eq(createUserId))
                .and(traceId, () -> traceId.endsWith("%") || traceId.startsWith("%") ? table.traceId.like(traceId) : table.traceId.eq(traceId))
//                .and(message, () -> message.endsWith("%") || message.startsWith("%") ? table.message.like(message) : table.message.contains(message))
//                .and(createTime, () -> createTime.endsWith("%") || createTime.startsWith("%") ? table.createTime.like(createTime) : table.createTime.eq(createTime))
//                .and(updateTime, () -> updateTime.endsWith("%") || updateTime.startsWith("%") ? table.updateTime.like(updateTime) : table.updateTime.eq(updateTime))
                .and(updateUserId, () -> table.updateUserId.eq(updateUserId))
                .and(table.deleted.eq(Optional.ofNullable(deleted).orElse(false)))
////                .and(phone, () -> table.phone.eq(phone))
////                .and(createUserId, () -> table.createUserId.eq(createUserId))
////                .and(updateUserId, () -> table.updateUserId.eq(updateUserId))
////                // 强制带默认值的查询字段
////                .and(table.deleted.eq(Optional.ofNullable(deleted).orElse(false)))
////                // 数字区间查询
////                .and(amountRange, () -> table.amount.between(amountRange.getMin(), amountRange.getMax()))
////                // 日期区间查询；Range.rebuild() : 先将时间区间重置到 00:00:00.000 - 23:59:59.999 ; 大多数情况都需要重置时间
////                .and(createTimeRange, () -> ExpressionUtils.and(createTimeRange.rebuild().ifPresentCreateTimeBegin(table.createTime::goe), createTimeRange.ifPresentCreateTimeEnd(table.createTime::loe)))
////                .and(updateTimeRange, () -> ExpressionUtils.and(updateTimeRange.rebuild().ifPresentUpdateTimeBegin(table.createTime::goe), updateTimeRange.ifPresentUpdateTimeEnd(table.createTime::loe)))
////                // 模糊匹配查询：后面带 % ；建议优先使用
////                .and(name, () -> table.name.startsWith(name)) // 模糊匹配查询：后面带 %
////                .and(name, () -> table.name.endsWith(name)) // 模糊匹配查询：前面带 %
////                .and(name, () -> table.name.contains(name)) // 模糊匹配查询：前后带 %,同 MessageFormat.format("%{0}%", name)
////                .and(name, () -> table.name.like(MessageFormat.format("%{0}%", name))) 模糊匹配查询：前后带 %
                ;
    }

    /**
     * 排序参数构建：QueryDSL 查询模式；QueryDSL 模式构建排序对象返回 null 则会抛出异常
     *
     * @return {@link OrderSpecifier[]}
     */
    public OrderSpecifier<?>[] qdslOrderBy() {
        try {
            if (CollectionUtils.isEmpty(orderBy)) {
                // return new OrderSpecifier<?>[]{OrderByColumn.id.column.asc()}; // 指定默认排序字段
                return new OrderSpecifier<?>[]{};
            }
            return orderBy.stream().map((OrderBy by) -> {
                final OrderByColumn column = OrderByColumn.valueOf(by.getName());
                // 自定义排序
                // new OrderSpecifier<>(Order.valueOf(by.getDirection().name()) , Expressions.stringTemplate("convert_gbk({0})", column.column));

                // return Objects.equals(orderBy.getDirection(), Sort.Direction.DESC) ? column.column.desc(): column.column.asc();
                return new OrderSpecifier<>(
                        Order.valueOf(by.getDirection().name())
                        , column.column
                );
            }).toArray(OrderSpecifier<?>[]::new);
        } catch (Exception e) {
            throw Code.A00003.toCodeException("排序字段可选范围：".concat(JSON.toJSONString(OrderByColumn.names())));
        }
    }

    /**
     * 排序参数构建：Spring JPA 查询模式；JPA 模式构建排序对象返回 null 表示不排序
     *
     * @return {@link Sort}
     */
    public Sort jpaOrderBy() {
        try {
            if (CollectionUtils.isEmpty(orderBy)) {
                // return Sort.by(Sort.Direction.ASC, OrderByColumn.id.name()); // 指定默认排序字段
                return Sort.unsorted();
            }
            return orderBy.stream()
                    .map((OrderBy by) -> {
                        final OrderByColumn column = OrderByColumn.valueOf(by.getName());
                        // 自定义排序
                        // JpaSort.unsafe(by.getDirection(), String.format("convert_gbk(%s)", column.name()));
                        return Sort.by(by.getDirection(), column.name());
                    })
                    .reduce(Sort::and)
                    .orElseGet(Sort::unsorted);
        } catch (Exception e) {
            throw Code.A00003.toCodeException("排序字段可选范围：".concat(JSON.toJSONString(OrderByColumn.names())));
        }
    }

    /**
     * 获取查询实体与数据库表映射的所有字段,用于投影到 VO 类
     * 支持追加扩展字段,追加扩展字段一般用于连表查询
     *
     * @param appends {@link Expression}[] 追加扩展连表查询字段
     * @return {@link Expression}[]
     */
    public static Expression<?>[] allColumnAppends(final Expression<?>... appends) {
        final List<Expression<?>> columns = Lists.newArrayList(appends);
        final Class<?> clazz = tabErrorLog.getClass();
        try {
            for (Field field : clazz.getDeclaredFields()) {
                if (field.getType().isPrimitive()) continue;
                final Object o = field.get(tabErrorLog);
                if (o instanceof EntityPath || o instanceof BeanPath) continue;
                if (o instanceof Path) {
                    columns.add((Path<?>) o);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("获取查询实体属性与数据库映射的字段异常", e);
        }
        return columns.toArray(new Expression<?>[0]);
    }

// DB End **************************************************************************************************************

}
/* yml 缓存配置
spring:
  app:
    cache:
      tab-error-log: # ITabErrorLogCache.CACHE_ROW_BY_ID 缓存配置
        expired: 1d
        heap: 1000
        offheap: 1
        disk: 100
*/
/* ehcache 配置
.withCache(ITabErrorLogCache.CACHE_ROW_BY_ID, CacheConfigurationBuilder
        .newCacheConfigurationBuilder(Long.class, TabErrorLog.class, ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(appConfig.getCache().getTabErrorLog().getHeap(), EntryUnit.ENTRIES) // 堆内内存，没有反序列化性能消耗，但是不能对读取的缓存对象执行 set 操作
                .offheap(appConfig.getCache().getTabErrorLog().getOffheap(), MemoryUnit.MB) // 堆外内存，有反序列化消耗，同样不能对读取的对象执行 set 操作
                .disk(appConfig.getCache().getTabErrorLog().getDisk(), MemoryUnit.MB, true) // 缓存到磁盘，指定缓存文件最大值
        )
        .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(appConfig.getCache().getTabErrorLog().getExpired())) // 过期时间
        .withValueSerializer(new FastJsonEhcacheSerializer<>(TabErrorLog.class)) // 序列化规则
)
*/
/* redis 配置
.withCacheConfiguration(ITabErrorLogCache.CACHE_ROW_BY_ID, RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(appConfig.getCache().getTabErrorLog().getExpired()) // 设置过期时间
        .disableCachingNullValues() // 设置缓存 null 值
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new CustomFastJsonRedisSerializer<>(TabErrorLog.class)))
)
*/
/*
import com.alibaba.fastjson.annotation.JSONField;
import com.querydsl.core.annotations.QueryTransient;
import com.support.mvc.entity.ICache;
import java.beans.Transient;

import static com.ccx.demo.config.init.AppInit.getBean;
/**
 * 缓存：异常日志记录
 *
 * @author 谢长春 on 2022-02-16 V20220301
 *\/
public interface ITabErrorLogCache extends ICache {
    String CACHE_ROW_BY_ID = "ITabErrorLogCache";

    /**
     * 按 ID 获取数据缓存行
     *
     * @param id {@link TabErrorLog#getId()}
     * @return {@link Optional<TabErrorLog>}
     *\/
    @JSONField(serialize = false, deserialize = false)
    default Optional<TabErrorLog> getTabErrorLogCacheById(final Long id) {
        return Optional.ofNullable(id)
                .filter(v -> v > 0)
                .map(v -> getBean(ErrorLogRepository.class).findCacheById(v));
    }
}
*/
